/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2021. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ using Leap.Interaction.Internal.InteractionEngineUtility; using System.Collections.Generic; using UnityEngine; namespace Leap.Unity.Interaction { /// /// The sliding window throw handler implements a simple heuristic that provides a /// reasonably accurate measure of the user's intended "throw direction" for a physical /// object. It is used as the default implementation of an Interaction Behaviour's /// throw handler. /// public class SlidingWindowThrow : IThrowHandler { /// /// The length of the averaging window in seconds. /// private float _windowLength = 0.05F; /// /// The delay between the averaging window and the current time. /// private float _windowDelay = 0.02F; /// /// A curve that maps the speed of the object upon release to a multiplier to apply /// to that speed as the throw occurs. /// private AnimationCurve _velocityMultiplierCurve = new AnimationCurve( new Keyframe(0.0F, 1.0F, 0, 0), new Keyframe(3.0F, 1.5F, 0, 0)); private struct VelocitySample { public float time; public Vector3 position; public Quaternion rotation; public VelocitySample(Vector3 position, Quaternion rotation, float time) { this.position = position; this.rotation = rotation; this.time = Time.fixedTime; } public static VelocitySample Interpolate(VelocitySample a, VelocitySample b, float time) { float alpha = Mathf.Clamp01(Mathf.InverseLerp(a.time, b.time, time)); return new VelocitySample(Vector3.Lerp(a.position, b.position, alpha), Quaternion.Slerp(a.rotation, b.rotation, alpha), time); } } private Queue _velocityQueue = new Queue(64); /// /// Samples the current velocity and adds it to a rolling average. /// public void OnHold(InteractionBehaviour intObj, ReadonlyList controllers) { _velocityQueue.Enqueue(new VelocitySample(intObj.rigidbody.position, intObj.rigidbody.rotation, Time.fixedTime)); while (true) { VelocitySample oldestVelocity = _velocityQueue.Peek(); // Dequeue conservatively if the oldest velocity is more than 4 frames later // than the start of the window. if (oldestVelocity.time + (Time.fixedDeltaTime * 4) < Time.fixedTime - _windowLength - _windowDelay) { _velocityQueue.Dequeue(); } else { break; } } } /// /// Transfers the averaged velocity to the released object. /// public void OnThrow(InteractionBehaviour intObj, InteractionController throwingController) { if (_velocityQueue.Count < 2) { intObj.rigidbody.velocity = Vector3.zero; intObj.rigidbody.angularVelocity = Vector3.zero; return; } float windowEnd = Time.fixedTime - _windowDelay; float windowStart = windowEnd - _windowLength; // 0 occurs before 1, // start occurs before end. VelocitySample start0, start1; VelocitySample end0, end1; VelocitySample s0, s1; s0 = s1 = start0 = start1 = end0 = end1 = _velocityQueue.Dequeue(); while (_velocityQueue.Count != 0) { s0 = s1; s1 = _velocityQueue.Dequeue(); if (s0.time < windowStart && s1.time >= windowStart) { start0 = s0; start1 = s1; } if (s0.time < windowEnd && s1.time >= windowEnd) { end0 = s0; end1 = s1; // We have assigned both start and end and can break out of the loop. _velocityQueue.Clear(); break; } } VelocitySample start = VelocitySample.Interpolate(start0, start1, windowStart); VelocitySample end = VelocitySample.Interpolate(end0, end1, windowEnd); Vector3 interpolatedVelocity = PhysicsUtility.ToLinearVelocity(start.position, end.position, _windowLength); intObj.rigidbody.velocity = interpolatedVelocity; intObj.rigidbody.angularVelocity = PhysicsUtility.ToAngularVelocity(start.rotation, end.rotation, _windowLength); intObj.rigidbody.velocity *= _velocityMultiplierCurve.Evaluate(intObj.rigidbody.velocity.magnitude); } } }